/**
* Author: Bob Chen
*/

package net.uman.framework.modules.objectpool;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.uman.entity.IdEntity;
import net.uman.framework.modules.utils.ReflectionUtils;

import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.Hibernate;

public class MapAction extends Action {
    public BeanObject getBeanObject(IdEntity obj) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    	BeanObject bean = new BeanObject(getModelName(obj));
    	bean.set("id", obj.getId());
    	
        Map<IdEntity, BeanObject> beans = new HashMap<IdEntity, BeanObject>();          
        beans.put(obj, bean);
        
        bean.setValues(getProperties(obj, beans));
    	return bean;
    }
    
    public IdEntity getModel(BeanObject bean) {
    	IdEntity obj = createObject(bean.getModelName());
    	
        Map<BeanObject, IdEntity> beans = new HashMap<BeanObject, IdEntity>();          
        beans.put(bean, obj);

        copyProperties(bean.getValues(), obj, beans);
    	return obj;
    }
    
    protected Map<String, Object> getProperties(IdEntity obj, Map<IdEntity, BeanObject> beans) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    	System.out.println("getProperties("+obj);
        Map<String, Object> props = new HashMap<String, Object>();
       
        Field[] fields = getAllFields(obj.getClass());        
        
        for (int i = 0 ; i < fields.length ; i++) {
            if (Modifier.isStatic(fields[i].getModifiers())) {
                continue;
            }
            
            String name = fields[i].getName();
            System.out.println("before getBeanProperty");
            Object value = getBeanProperty(obj, name);
            System.out.println("getBeanProperty obj:"+obj+" name:"+name+" value:"+value);
            
            if (value instanceof IdEntity) {
        		System.out.println("Hibernate.isInitialized("+Hibernate.isInitialized(value));
            	if (Hibernate.isInitialized(value)) {
            		BeanObject _o = getBeanObject(beans, (IdEntity)value);
            		if (_o == null) {
            			_o = new BeanObject(getModelName((IdEntity)value), getProperties((IdEntity)value, beans));
            			beans.put((IdEntity)value, _o);
            		}
            		props.put(name, _o);
            	} else {
	            	Long id = ((IdEntity)value).getId();
	                props.put(name, id);
            	}
            } else if (value instanceof Collection) {
                List list = new ArrayList();
                Class type = ReflectionUtils.getGenricType(fields[i]);
                String mname = type.getSimpleName();
                for (Iterator itv = ((Collection)value).iterator(); itv.hasNext();) {
                    IdEntity o = (IdEntity) itv.next();
                    if (Hibernate.isInitialized(o)) {
                		BeanObject _o = getBeanObject(beans, o);
                		if (_o == null) {
                			_o = new BeanObject(getModelName(o), getProperties(o, beans));
                			beans.put(o, _o);
                		}
                		list.add(_o);
                    } else {
	                    Long id = o.getId();
	                    list.add(mname+":"+id);
	                }
                }
                
                props.put(name, list);
            } else if (value instanceof Date) {
                props.put(name, ((Date)value).getTime());
            } else if (value != null){
                props.put(name, value);
            }
        }

        return props;
    }
    
    private BeanObject getBeanObject(Map<IdEntity, BeanObject> beans, IdEntity obj) {
    	for (IdEntity entity : beans.keySet()) {
    		String cls1 = entity.getClass().getSimpleName();
    		if (cls1.contains("_$$_")) {
    			cls1 = entity.getClass().getSuperclass().getSimpleName();
    		}
    		String cls2 = obj.getClass().getSimpleName();
    		if (cls2.contains("_$$_")) {
    			cls2 = obj.getClass().getSuperclass().getSimpleName();
    		}
    		
    		if (cls1.equals(cls2) && entity.getId().equals(obj.getId())) {
    			return beans.get(entity);
    		}
    	}
    	return null;
    }
    
    private Field[] getAllFields(Class cls) {
        Field[] fields = cls.getDeclaredFields();
        if (fields == null) {
            fields = new Field[0];
        }
        
        Class scls = cls.getSuperclass();
        if (scls != null && scls != Object.class) {
            Field[] _fields = scls.getDeclaredFields();
            if (_fields != null) {
                ArrayList<Field> v = new ArrayList<Field>();
                for (Field f : fields) {
                    v.add(f);
                }
                for (Field f : _fields) {
                    v.add(f);
                }
                fields = v.toArray(new Field[0]);
            }
        }
        
        return fields;
    }
    
    protected void copyProperties(Map<String, Object> props, IdEntity obj, Map<BeanObject, IdEntity> beans) {
        HashMap<String, Object> _props = new HashMap<String, Object>(); 
        Field[] fields = getAllFields(obj.getClass());
        for (int i = 0; i < fields.length; i++) {
            if (Modifier.isStatic(fields[i].getModifiers())) {
                continue;
            }
            
            Field field = fields[i];
            String fn = field.getName();
            System.out.print(field.getName());
            Class type = field.getType(); 
//            System.out.println("fn:"+fn+" field.getType():"+field.getType());
            if (!props.containsKey(fn)) {
                continue;
            }
            
            Object value = props.get(fn);
            if (value == null) {
            	_props.put(fn, value);
            	continue;
            }

            if (IdEntity.class.isAssignableFrom(type)) {
                String bean = type.getSimpleName();
                if (value instanceof Long) {
                    // the value is ID
                    IdEntity mo = getObject(bean, (Long)value);
                    _props.put(fn, mo);
                } else if (value instanceof BeanObject) {
                	BeanObject bo = (BeanObject)value;
                    IdEntity mo = getBeanObject(beans, bo);
                    if (mo == null) {
                    	mo = getModel(bo);
                    	beans.put(bo, mo);
                    }
                    _props.put(fn, mo);             
                } else {
                    throw new RuntimeException("Unknown value type: " + value+","+value.getClass()+" bean:"+bean);
                }    
            } else if (Collection.class.isAssignableFrom(type)) {
                List<IdEntity> set = new ArrayList<IdEntity>();
                if (value != null) {
                    if (value instanceof List) {
                        // the value is model:ID1,ID2,...,IDn
                    	for (Object o : (List)value) {
                    		if (o instanceof String) {
                    			String s = (String)o;
		                    	String bean = s.substring(0, s.indexOf(":"));
		//                    	System.out.println("value:"+value);
		                    	s = s.substring(bean.length() + 1);
	                            IdEntity mo = getObject(bean, new Long(s));
	                            set.add(mo);
                    		} else if (o instanceof BeanObject) {
                            	BeanObject bo = (BeanObject)o;
                                IdEntity mo = getBeanObject(beans, bo);
                                if (mo == null) {
                                	mo = getModel(bo);
                                	beans.put(bo, mo);
                                }
                                set.add(mo);
                    		} else {
                    			throw new RuntimeException("Unknown value type: " + o+","+o.getClass()+" fn:"+fn);
                    		}
                    	}
                    } else {
                        throw new RuntimeException("Unknown value type: " + value+","+value.getClass()+" fn:"+fn);
                    }
                }
                _props.put(fn, set);
            } else if (Timestamp.class.isAssignableFrom(type)) {
                _props.put(fn, new Timestamp((Long)value));
            } else if (Time.class.isAssignableFrom(type)) {
                _props.put(fn, new Time((Long)value));
            } else if (java.sql.Date.class.isAssignableFrom(type)) {
                _props.put(fn, new java.sql.Date((Long)value));
            } else {
                _props.put(fn, value);
            }
        }
        
//        System.out.println("_props:"+_props);
        try {
        	for (String pname : _props.keySet()) {
        		Method method = obj.getClass().getMethod("set"+firstUpperCase(pname));
        		method.invoke(obj, _props.get(pname));
//        		BeanUtils.setProperty(obj, pname, _props.get(pname));
        	}
//            BeanUtils.populate(obj, _props);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 
    
    private IdEntity getBeanObject(Map<BeanObject, IdEntity> beans, BeanObject bean) {
    	for (BeanObject _bean : beans.keySet()) {
    		if (_bean.getModelName().equals(bean.getModelName()) && bean.getID().equals(_bean.getID())) {
    			return beans.get(_bean);
    		}
    	}
    	return null;
    }
    
    /**
     * Such as gallery.setGoods(goods)
     */
    private void setOwnerTo(IdEntity obj, IdEntity owner) {
        String methodName = "set" + getModelName(owner);
        try {
            Method method = obj.getClass().getMethod(methodName, owner.getClass());
            if (method  == null) {
                System.out.println("Method not found: " + methodName);
                return;
            }
            
            method.invoke(obj, owner);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
